use ctapi::constant::c::CONFIG_FILE_NAME;
use ctapi::router::channel_setting_router::rsp::GetChannelInfoResponse;
use ctapi::router::common_router::rsp::GetUpdateInfoResponse;
use serde::de::Error;
use serde::{Deserialize, Deserializer, Serializer};
use share::core::channel::Channel;
use share::core::device_id::DeviceId;
use share::core::native_version::NativeVersion;
use std::borrow::Borrow;
use std::env;
use std::ops::Not;
use std::process::{Child, Command};
use std::str::FromStr;
use std::sync::Arc;
use std::time::Duration;
use tokio::sync::{Mutex, OnceCell};
use tokio::time;
use url::Url;

pub struct Server {
    _daemon: Child,
    base_url: Url,
}

impl Server {
    pub async fn new() -> std::io::Result<Self> {
        let bin = env::current_exe()?
            .parent()
            .and_then(|p| p.parent())
            .expect("Bin directory")
            .join("ctapi")
            .to_path_buf();

        let mut cmd = Command::new(bin);
        cmd.arg("run");
        println!("{:?}", cmd);
        let daemon = cmd.spawn()?;

        time::sleep(Duration::from_secs(1)).await;

        let base_url = Url::parse("http://localhost:11850").unwrap();

        Ok(Self {
            _daemon: daemon,
            base_url,
        })
    }

    pub fn base_url(&self) -> &Url {
        &self.base_url
    }

    pub fn url(&self, path: &str) -> Url {
        self.base_url.join(path).unwrap()
    }

    pub async fn get(&self, p: impl AsRef<str>) -> String {
        let url = self.url(p.as_ref());
        let response = reqwest::get(url.clone())
            .await
            .expect(&format!("Failed to get url: {}", url.clone()));

        if !response.status().is_success() {
            let status = response.status();
            let rsp_text = response.text().await.unwrap_or("No body".to_string());
            panic!(
                "Failed to get url: {}, status: {}, response: {}",
                url, status, rsp_text
            );
        }

        response.text().await.unwrap()
    }

    pub async fn fetch_update_info(
        &self,
        channel: Channel,
        native_version: NativeVersion,
        device_id: DeviceId,
    ) -> GetUpdateInfoResponse {
        let p = format!(
            "/update_info?channel={}&version={}&device_id={}",
            channel, native_version, device_id
        );
        let s = self.get(p).await;

        serde_json::from_str(&s).unwrap()
    }

    pub async fn fetch_channel_info(&self, channel: Channel) -> GetChannelInfoResponse {
        let p = format!("/channel_setting/info/{}", channel);
        let s = self.get(p).await;
        serde_json::from_str(&s).unwrap()
    }

    pub async fn post_json(&mut self, p: impl AsRef<str>, json: impl AsRef<str>) {
        let url = self.url(p.as_ref());
        let req_json = json.as_ref();
        let client = reqwest::Client::new();
        let response = client
            .post(url.clone())
            .body(req_json.to_string())
            .send()
            .await
            .unwrap();

        if response.status().is_success().not() {
            let status = response.status();
            let rsp_text = response.text().await.unwrap_or("No body".to_string());
            panic!(
                "Failed to post json, status:{:?}, url:{:?}\n    request:{:?}\n    response:{:?}",
                status, url, req_json, rsp_text
            );
        };
    }

    pub async fn put_json(&mut self, p: impl AsRef<str>, json: impl Borrow<str>) {
        let url = self.url(p.as_ref());
        let req_json = json.borrow().to_owned();
        let client = reqwest::Client::new();
        let response = client
            .put(url.clone())
            .body(req_json.clone())
            .send()
            .await
            .unwrap();

        if response.status().is_success().not() {
            let status = response.status();
            let rsp_text = response.text().await.unwrap_or("No body".to_string());
            panic!(
                "Failed to put json, status:{:?}, url:{:?}\n    request:{:?}\n    response:{:?}",
                status, url, req_json, rsp_text
            );
        };
    }

    pub async fn delete(&mut self, p: impl AsRef<str>) {
        let url = self.url(p.as_ref());
        let client = reqwest::Client::new();
        let response = client.delete(url.clone()).send().await.unwrap();

        if response.status().is_success().not() {
            let status = response.status();
            let rsp_text = response.text().await.unwrap_or("No body".to_string());
            panic!(
                "Failed to put json, status:{:?}, url:{:?}\n    response:{:?}",
                status, url, rsp_text
            );
        };
    }
}

impl Drop for Server {
    fn drop(&mut self) {
        if let Err(e) = self._daemon.kill() {
            eprintln!("Failed to stop server: {}", e)
        }

        if let Err(e) = self._daemon.wait() {
            eprintln!("Failed to wait server exit: {}", e)
        }
    }
}

#[derive(Deserialize, Eq, PartialEq, Debug)]
pub struct UpdateInfo {
    pub channel: Channel,
    pub min_native_version: u32,
    pub latest_native_version: u32,
    pub raw_resource_version: String,
    pub default_resource_version: String,
    pub jump_url: String,
    pub resource_url: String,
}

static SHARED_SERVER: OnceCell<Arc<Mutex<Server>>> = OnceCell::const_new();

pub async fn shared_server() -> Arc<Mutex<Server>> {
    SHARED_SERVER
        .get_or_init(|| async {
            let server = Server::new().await.unwrap();
            Arc::new(Mutex::new(server))
        })
        .await
        .clone()
}
